package com.garbagecode.util.pagination;

import java.lang.reflect.Field;
import java.util.List;

import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.session.SqlSession;

/**
 * 
 * @author cc.z
 *
 */
public class ResultBoundsUtils {
	private static Field keyPropertiesField;
	private static Field keyColumnsField;
	private static Field resultSetsField;
	private static SqlSession sqlSession;

	static {
		init();
	}

	private static void init() {
		Class<?> cls = MappedStatement.class;
		try {
			keyPropertiesField = cls.getDeclaredField("keyProperties");
			keyPropertiesField.setAccessible(true);

			keyColumnsField = cls.getDeclaredField("keyColumns");
			keyColumnsField.setAccessible(true);

			resultSetsField = cls.getDeclaredField("resultSets");
			resultSetsField.setAccessible(true);
		} catch (Exception e) {
			throw new RuntimeException("reflect field of class '" + cls.getName() + "' fail", e);
		}
	}

	public static MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource,
			List<ResultMap> resultMaps) {
		MappedStatement.Builder statementBuilder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(),
				newSqlSource, ms.getSqlCommandType());

		statementBuilder.resource(ms.getResource());
		statementBuilder.fetchSize(ms.getFetchSize());
		statementBuilder.statementType(ms.getStatementType());
		statementBuilder.keyGenerator(ms.getKeyGenerator());
		statementBuilder.databaseId(ms.getDatabaseId());
		statementBuilder.lang(ms.getLang());
		statementBuilder.resultOrdered(ms.isResultOrdered());

		// setStatementTimeout
		statementBuilder.timeout(ms.getTimeout());

		// setStatementParameterMap
		statementBuilder.parameterMap(ms.getParameterMap());

		// setStatementResultMap
		resultMaps = (resultMaps == null) ? ms.getResultMaps() : resultMaps;
		statementBuilder.resultMaps(resultMaps);

		statementBuilder.resultSetType(ms.getResultSetType());

		// setStatementCache
		statementBuilder.cache(ms.getCache());
		statementBuilder.flushCacheRequired(ms.isFlushCacheRequired());
		statementBuilder.useCache(ms.isUseCache());

		MappedStatement statement = statementBuilder.build();

		try {
			keyPropertiesField.set(statement, ms.getKeyProperties());
			keyColumnsField.set(statement, ms.getKeyColumns());
			resultSetsField.set(statement, ms.getResulSets());
		} catch (Exception e) {
			throw new RuntimeException(e);
		}

		return statement;
	}

	public static SqlSession getSqlSession() {
		if (sqlSession == null) {
			throw new RuntimeException("property 'sqlSession' is required");
		}
		
		return sqlSession;
	}

	public static class InjectSqlSessionBean {
		public void setSqlSession(SqlSession sqlSession) {
			ResultBoundsUtils.sqlSession = sqlSession;
		}
	}

}
